package com.yfqy.admin.security;

import com.yfqy.admin.security.exception.CustomAuthenticationExceptionHandler;
import com.yfqy.admin.security.exception.CustomAuthorizationExceptionHandler;
import com.yfqy.admin.security.exception.CustomSecurityExceptionHandler;
import com.yfqy.admin.security.login.LoginFailHandler;
import com.yfqy.admin.security.login.LoginSuccessHandler;
import com.yfqy.admin.security.login.sms.SmsAuthenticationFilter;
import com.yfqy.admin.security.login.sms.SmsAuthenticationProvider;
import com.yfqy.admin.security.login.username.UsernameAuthenticationFilter;
import com.yfqy.admin.security.login.username.UsernameAuthenticationProvider;
import com.yfqy.admin.security.resourceapi.internal.MyJwtAuthenticationFilter;
import com.yfqy.admin.service.JwtService;
import jakarta.servlet.Filter;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.List;

/**
 * @Author: 梁二东
 * @Date: 2025/5/3 12:30
 * @Description: 基于spring security 的 SecurityFilterChain 配置不同url的认证策略
 **/
@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    private final ApplicationContext applicationContext;

    public SecurityConfig(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // 身份认证失败
    private final AuthenticationEntryPoint authenticationExceptionHandler = new CustomAuthenticationExceptionHandler();
    // 权限认证失败
    private final AccessDeniedHandler authorizationExceptionHandler = new CustomAuthorizationExceptionHandler();
    // Spring security filter chain 中抛出的未知异常
    private final Filter globalSpringSecurityExceptionHandler = new CustomSecurityExceptionHandler();

    private void commonConfig(HttpSecurity http) throws Exception {
        // 禁用SpringSecurity默认filter。这些filter都是非前后端分离项目的产物，用不上.
        // yml配置文件将日志设置DEBUG模式，就能看到加载了哪些filter
        // logging:
        //    level:
        //       org.springframework.security: DEBUG
        // 表单登录/登出、session管理、csrf防护等默认配置，如果不disable。会默认创建默认filter
        http.formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                // requestCache用于重定向，前后端分析项目无需重定向，requestCache也用不上
                .requestCache(cache -> cache
                        .requestCache(new NullRequestCache())
                )
                // 无需给用户一个匿名身份
                .anonymous(AbstractHttpConfigurer::disable);

        // 处理 SpringSecurity 异常响应结果。响应数据的结构，改成业务统一的JSON结构。不要框架默认的响应结构
        http.exceptionHandling(exceptionHandling ->
                exceptionHandling
                        // 认证失败异常
                        .authenticationEntryPoint(authenticationExceptionHandler)
                        // 鉴权失败异常
                        .accessDeniedHandler(authorizationExceptionHandler)
        );
        // 其他未知异常. 尽量提前加载。SecurityContextHolderFilter 是属于springboot3.x.x版本的
        http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);

        // springboot.version < 3.0
        // http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextPersistenceFilter.class);
    }

    /**
     * 密码加密使用的编码器
     */
    @Bean("BCryptPasswordEncoder")
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
        commonConfig(http);
        // 使用securityMatcher限定当前配置作用的路径
        http.securityMatcher("/api/user/login/*")
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
        // springboot.version < 3.0
        // http.antMatcher("/user/login/*").authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
        LoginSuccessHandler loginSuccessHandler = applicationContext.getBean(LoginSuccessHandler.class);
        LoginFailHandler loginFailHandler = applicationContext.getBean(LoginFailHandler.class);

        // 加一个用户名密码登录
        UsernameAuthenticationFilter usernameLoginFilter = new UsernameAuthenticationFilter(
                new AntPathRequestMatcher("/api/user/login/username", HttpMethod.POST.name()),
                new ProviderManager((applicationContext.getBean(UsernameAuthenticationProvider.class))),
                loginSuccessHandler,
                loginFailHandler);
        http.addFilterBefore(usernameLoginFilter, UsernamePasswordAuthenticationFilter.class);

        // 加一个短信验证码登录
        SmsAuthenticationFilter smsLoginFilter = new SmsAuthenticationFilter(
                new AntPathRequestMatcher("/api/user/login/sms", HttpMethod.POST.name()),
                new ProviderManager(applicationContext.getBean(SmsAuthenticationProvider.class)),
                loginSuccessHandler,
                loginFailHandler);
        http.addFilterBefore(smsLoginFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /**
     * /api/internal/** B端接口url
     * /api/customer/** C端接口url
     */
    @Bean
    public SecurityFilterChain myApiFilterChain(HttpSecurity http) throws Exception {
        commonConfig(http);
        // 使用securityMatcher限定当前配置作用的路径
        http.securityMatcher("/api/internal/**", "/api/consumer/**")
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
        MyJwtAuthenticationFilter myJwtAuthenticationFilter = new MyJwtAuthenticationFilter(
                applicationContext.getBean(JwtService.class),
                applicationContext.getBean(RedissonClient.class));
        http.addFilterBefore(myJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /*@Bean
    public SecurityFilterChain thirdApiFilterChain(HttpSecurity http) throws Exception {
        commonConfig(http);
        // 不使用securityMatcher限定当前配置作用的路径。所有没有匹配上指定SecurityFilterChain的请求，都走这里鉴权
        http.securityMatcher("/api/business/**")
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());

        OpenApi2AuthenticationFilter openApiFilter = new OpenApi2AuthenticationFilter();
        // 加一个登录方式。用户名、密码登录
        http.addFilterBefore(openApiFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }*/

    /**
     * 不鉴权的api
     */
    @Bean
    public SecurityFilterChain publicApiFilterChain(HttpSecurity http) throws Exception {
        commonConfig(http);
        http
                // 使用securityMatcher限定当前配置作用的路径
                .securityMatcher("/api/noauth/**")
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
        return http.build();
    }

//  /** 其余路径，走这个默认过滤链 */
//  @Bean
//  @Order(value = Integer.MAX_VALUE) // 这个过滤链最后加载
//  public SecurityFilterChain defaultApiFilterChain(HttpSecurity http) throws Exception {
//    commonConfig(http);
//    http // 不用securityMatcher表示缺省值，匹配不上其他过滤链时，都走这个过滤链
//        .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
//    http.addFilterBefore(new OpenApi3AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
//    return http.build();
//  }

    /**
     * 注意事项
     * 1.Spring Security优先：如果同时使用多种方式，Spring Security的CORS配置会优先于其他方式。
     * 2.CSRF保护：如果启用CSRF保护并允许跨域请求，需要确保正确处理CSRF令牌。
     * 3.Credentials：当设置allowCredentials(true)时，不能使用allowedOrigins("*")，必须指定具体域名。
     *
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许的源（生产环境应具体指定）
        configuration.setAllowedOrigins(
                Arrays.asList("http://localhost:5173", "https://www.lianjingchenggou.com/")
        );
        // 允许的方法, "PUT", "DELETE", "OPTIONS", "PATCH"
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        // 允许的头部
        configuration.setAllowedHeaders(List.of("*"));
        // 是否允许凭据（如Cookie）
        configuration.setAllowCredentials(true);
        // 预检请求缓存时间（秒）
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
